package concordia.comp6231.failuredetection;

import static concordia.comp6231.common.ApplicationConfig.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Timer;

import concordia.comp6231.common.ApplicationConfig;
import concordia.comp6231.groupmembership.GroupMembershipManager;
import concordia.comp6231.leaderelection.LeaderElectionManager;
import concordia.comp6231.rfmulticast.Message;
import concordia.comp6231.rfmulticast.RFIFOMulticast;
import concordia.comp6231.rfmulticast.ResultHandler;
import concordia.comp6231.server.ReplicaMember;

public class FailureDetector implements IFailureDetector {

	static final String MSG_TYPE = "Msg_Type";
	static final String MSG_HEART_BEAT = "HeartBeat";
	static final String MSG_HEART_BEAT_RESPONSE = "HeartBeatResponse";
	static final String MSG_ALIVE = "Alive";

	static final String MSG_SUSPECT = "Suspect";
	static final String MSG_SUSPECTED_MEMBER = "SuspectedMember";
	static final String MSG_SUSPECT_CONFIRM = "SuspectConfirm";
	static final String MSG_SUSPECT_CONFIRM_RESPONSE = "SuspectConfirmResponse";
	static final String MSG_SUSPECT_REJECT = "SuspectReject";
	static final String MSG_SUSPECT_ACK = "SuspectaAck";
	static final String SUSPECT_REQ_ID = "SuspectReqId";
	private Map<String, Integer> suspectRequests = new HashMap<String, Integer>();

	// default value of heart beat freq. just in case reading from properties file failed
	private static final int DEFAULT_HEART_BEAT_FREQUENCY = 10;
	protected static final String MSG_ORIGINAL_SENDER = "OriginalSender";

	private int heartBeatFrequencyMS;

	public FailureDetector() {
	}

	@Override
	public void start() {
		if (ReplicaMember.currentInstance().isLeader()){
			heartBeatFrequencyMS = initHeartBeatFrequencyMS();
			Timer t = new Timer(this.heartBeatFrequencyMS, new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent arg0) {
					List<String> allMembersExceptMe = GroupMembershipManager.currentInstance().getOtherMembers();
					/*List<String> allMembersExceptMe = new ArrayList<String>();
					allMembersExceptMe.add("LYTTON");
					allMembersExceptMe.add("MANZANITA");*/
					if (allMembersExceptMe.size() == 0) {
						System.out.println("No members in group: " + ReplicaMember.currentInstance().getGroupName());
					} else {
						sendHeartBeat(allMembersExceptMe);
					}
				}
			});
			t.start();
		}
		// for listening
		RFIFOMulticast rfm = new RFIFOMulticast();
		FailureDetectorListener rh = new FailureDetectorListener();
		rfm.listen(ApplicationConfig.FAILUREDETECTION_SERVER_PORT_RECEIVE, rh);
	}

	private int initHeartBeatFrequencyMS() {
		int freq = DEFAULT_HEART_BEAT_FREQUENCY;
		freq = ApplicationConfig.getAsInt(FAILUREDETECTION_HEARTBEAT_FREQ);
		if (freq == -1) {
			freq = DEFAULT_HEART_BEAT_FREQUENCY;
		}
		freq *= 1000;
		return freq;
	}

	private void sendHeartBeat(final List<String> members) {
		System.out.println("INFO: Send heartbeat is called!");
		RFIFOMulticast rfm = new RFIFOMulticast(members);
		ResultHandler heartbeatAckHandler = new ResultHandler() {
			@Override
			public void handleResult(Message msg) {
				System.out.println("INFO: Heart beat sent & ack, all alive.");
			}
		};

		ResultHandler heartbeatFaultHandler = new ResultHandler() {
			@Override
			public void handleResult(Message msg) {
				System.out.println("ERROR: Heart beat failure : "
						+ msg.toString());
				String failedHost = msg.get(RFIFOMulticast.RFM_MSG_DEAD_HOST);
				if (failedHost != null && !failedHost.isEmpty()) {
					System.out.println("Suspect request will be sent to " + members + "to check suspected host " + failedHost);
					sendSuspect(failedHost, members);
				}
			}
		};

		Message msg = new Message();
		msg.put(MSG_TYPE, MSG_HEART_BEAT);
		/*rfm.send(ApplicationConfig.PORT_FAILURE_DETECTOR_SERVICE_SEND, msg,
				heartbeatAckHandler, heartbeatFaultHandler);*/
		
		rfm.send(ApplicationConfig.FAILUREDETECTION_SERVER_PORT_SEND, msg,
				heartbeatAckHandler, heartbeatFaultHandler);
	}

	private void sendSuspect(final String suspectedMember, final List<String> members) {
		List<String> aliveMembers = new ArrayList<String>(members);
		aliveMembers.remove(suspectedMember);
		String suspectId = new Long(new Date().getTime()).toString();
		Message msg = new Message();
		msg.put(MSG_TYPE, MSG_SUSPECT);
		msg.put(MSG_SUSPECTED_MEMBER, suspectedMember);
		msg.put(SUSPECT_REQ_ID, suspectId);
		msg.put("ALIVE_MEMBERS_COUNT", new Integer(aliveMembers.size()).toString());
		if (aliveMembers.isEmpty()) {
			System.out.println("Suspect request was cancelled due to no  ");
			return;
		}
		RFIFOMulticast rfm = new RFIFOMulticast(aliveMembers);
		ResultHandler suspectAckHandler = new ResultHandler() {
			@Override
			public void handleResult(Message msg) {
				String msgType = msg.get(MSG_TYPE);
				if (MSG_SUSPECT_ACK.equals(msgType)) {
					int aliveMembers = Integer.parseInt(msg.get("ALIVE_MEMBERS_COUNT"));
					String suspectId = msg.get(SUSPECT_REQ_ID);
					int receivedAck = suspectRequests.get(suspectId) + 1;
					suspectRequests.put(suspectId, receivedAck);
					if (receivedAck == aliveMembers) {
						System.out.println("MEMBER IS DEAD: " + suspectedMember);
						if (LeaderElectionManager.currentInstance().getNameLeader().equalsIgnoreCase(suspectedMember)) {
							LeaderElectionManager.currentInstance().electNewLeader();
						}
					} else if (MSG_SUSPECT_REJECT.equals(msgType)) {
						System.out.println("Ignore, suspected is still alive.");
						return;
					}
				}
			}
		};

		ResultHandler suspectFaultHandler = new ResultHandler() {
			@Override
			public void handleResult(Message msg) {
				System.err.println("Another one is down! : " + msg.toString());
			}
		};
		rfm.send(ApplicationConfig.FAILUREDETECTION_SERVER_PORT_SEND, msg, suspectAckHandler, suspectFaultHandler);
	}

	public int getHeartBeatFrequencyMS() {
		return this.heartBeatFrequencyMS;
	}

}
